use std::fmt;
use thiserror::Error;

/// Application error types
#[derive(Error, Debug)]
pub enum AppError {
    /// Configuration errors
    #[error("Configuration error: {0}")]
    Config(String),

    /// Agent management errors
    #[error("Agent error: {0}")]
    Agent(String),

    /// Embedding errors
    #[error("Embedding error: {0}")]
    Embedding(String),

    /// Image processing errors
    #[error("Image error: {0}")]
    Image(String),

    /// Tool execution errors
    #[error("Tool error: {0}")]
    Tool(String),

    /// MCP service errors
    #[error("MCP service error: {0}")]
    McpService(String),

    /// Network errors
    #[error("Network error: {0}")]
    Network(String),

    /// Database errors
    #[error("Database error: {0}")]
    Database(String),

    /// Validation errors
    #[error("Validation error: {0}")]
    Validation(String),

    /// Rate limiting errors
    #[error("Rate limit exceeded: {0}")]
    RateLimit(String),

    /// Authentication errors
    #[error("Authentication error: {0}")]
    Auth(String),

    /// Permission errors
    #[error("Permission denied: {0}")]
    Permission(String),

    /// Resource not found errors
    #[error("Resource not found: {0}")]
    NotFound(String),

    /// Internal errors
    #[error("Internal error: {0}")]
    Internal(String),

    /// External service errors
    #[error("External service error: {service}: {message}")]
    External { service: String, message: String },

    /// Timeout errors
    #[error("Timeout error: {0}")]
    Timeout(String),

    /// Serialization errors
    #[error("Serialization error: {0}")]
    Serialization(String),
}

impl AppError {
    /// Create a configuration error
    pub fn config<S: Into<String>>(msg: S) -> Self {
        AppError::Config(msg.into())
    }

    /// Create an agent error
    pub fn agent<S: Into<String>>(msg: S) -> Self {
        AppError::Agent(msg.into())
    }

    /// Create an embedding error
    pub fn embedding<S: Into<String>>(msg: S) -> Self {
        AppError::Embedding(msg.into())
    }

    /// Create an image error
    pub fn image<S: Into<String>>(msg: S) -> Self {
        AppError::Image(msg.into())
    }

    /// Create a tool error
    pub fn tool<S: Into<String>>(msg: S) -> Self {
        AppError::Tool(msg.into())
    }

    /// Create an MCP service error
    pub fn mcp_service<S: Into<String>>(msg: S) -> Self {
        AppError::McpService(msg.into())
    }

    /// Create a network error
    pub fn network<S: Into<String>>(msg: S) -> Self {
        AppError::Network(msg.into())
    }

    /// Create a database error
    pub fn database<S: Into<String>>(msg: S) -> Self {
        AppError::Database(msg.into())
    }

    /// Create a validation error
    pub fn validation<S: Into<String>>(msg: S) -> Self {
        AppError::Validation(msg.into())
    }

    /// Create a rate limit error
    pub fn rate_limit<S: Into<String>>(msg: S) -> Self {
        AppError::RateLimit(msg.into())
    }

    /// Create an authentication error
    pub fn auth<S: Into<String>>(msg: S) -> Self {
        AppError::Auth(msg.into())
    }

    /// Create a permission error
    pub fn permission<S: Into<String>>(msg: S) -> Self {
        AppError::Permission(msg.into())
    }

    /// Create a not found error
    pub fn not_found<S: Into<String>>(msg: S) -> Self {
        AppError::NotFound(msg.into())
    }

    /// Create an internal error
    pub fn internal<S: Into<String>>(msg: S) -> Self {
        AppError::Internal(msg.into())
    }

    /// Create an external service error
    pub fn external<S: Into<String>, T: Into<String>>(service: S, message: T) -> Self {
        AppError::External {
            service: service.into(),
            message: message.into(),
        }
    }

    /// Create a timeout error
    pub fn timeout<S: Into<String>>(msg: S) -> Self {
        AppError::Timeout(msg.into())
    }

    /// Create a serialization error
    pub fn serialization<S: Into<String>>(msg: S) -> Self {
        AppError::Serialization(msg.into())
    }

    /// Get the error category
    pub fn category(&self) -> ErrorCategory {
        match self {
            AppError::Config(_) => ErrorCategory::Config,
            AppError::Agent(_) => ErrorCategory::Agent,
            AppError::Embedding(_) => ErrorCategory::Embedding,
            AppError::Image(_) => ErrorCategory::Image,
            AppError::Tool(_) => ErrorCategory::Tool,
            AppError::McpService(_) => ErrorCategory::McpService,
            AppError::Network(_) => ErrorCategory::Network,
            AppError::Database(_) => ErrorCategory::Database,
            AppError::Validation(_) => ErrorCategory::Validation,
            AppError::RateLimit(_) => ErrorCategory::RateLimit,
            AppError::Auth(_) => ErrorCategory::Auth,
            AppError::Permission(_) => ErrorCategory::Permission,
            AppError::NotFound(_) => ErrorCategory::NotFound,
            AppError::Internal(_) => ErrorCategory::Internal,
            AppError::External { .. } => ErrorCategory::External,
            AppError::Timeout(_) => ErrorCategory::Timeout,
            AppError::Serialization(_) => ErrorCategory::Serialization,
        }
    }

    /// Check if this error is retryable
    pub fn is_retryable(&self) -> bool {
        match self {
            AppError::Network(_) => true,
            AppError::Timeout(_) => true,
            AppError::External {
                service,
                message: _,
            } => {
                // Some external service errors are retryable
                matches!(service.as_str(), "openai" | "anthropic" | "gemini")
            }
            AppError::RateLimit(_) => true,
            _ => false,
        }
    }

    /// Get suggested retry delay in milliseconds
    pub fn retry_delay_ms(&self) -> u64 {
        match self {
            AppError::RateLimit(_) => 5000,    // 5 seconds for rate limits
            AppError::Timeout(_) => 1000,      // 1 second for timeouts
            AppError::Network(_) => 2000,      // 2 seconds for network errors
            AppError::External { .. } => 1000, // 1 second for external errors
            _ => 0,                            // No retry for other errors
        }
    }

    /// Get HTTP status code equivalent
    pub fn http_status(&self) -> u16 {
        match self {
            AppError::Validation(_) => 400,
            AppError::Auth(_) => 401,
            AppError::Permission(_) => 403,
            AppError::NotFound(_) => 404,
            AppError::RateLimit(_) => 429,
            AppError::Internal(_) => 500,
            AppError::External { .. } => 502,
            AppError::Timeout(_) => 504,
            _ => 500,
        }
    }
}

/// Error categories for better error handling
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ErrorCategory {
    /// Configuration errors
    Config,
    /// Agent management errors
    Agent,
    /// Embedding errors
    Embedding,
    /// Image processing errors
    Image,
    /// Tool execution errors
    Tool,
    /// MCP service errors
    McpService,
    /// Network errors
    Network,
    /// Database errors
    Database,
    /// Validation errors
    Validation,
    /// Rate limiting errors
    RateLimit,
    /// Authentication errors
    Auth,
    /// Permission errors
    Permission,
    /// Resource not found errors
    NotFound,
    /// Internal errors
    Internal,
    /// External service errors
    External,
    /// Timeout errors
    Timeout,
    /// Serialization errors
    Serialization,
}

impl fmt::Display for ErrorCategory {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ErrorCategory::Config => write!(f, "Configuration"),
            ErrorCategory::Agent => write!(f, "Agent"),
            ErrorCategory::Embedding => write!(f, "Embedding"),
            ErrorCategory::Image => write!(f, "Image"),
            ErrorCategory::Tool => write!(f, "Tool"),
            ErrorCategory::McpService => write!(f, "MCP Service"),
            ErrorCategory::Network => write!(f, "Network"),
            ErrorCategory::Database => write!(f, "Database"),
            ErrorCategory::Validation => write!(f, "Validation"),
            ErrorCategory::RateLimit => write!(f, "Rate Limit"),
            ErrorCategory::Auth => write!(f, "Authentication"),
            ErrorCategory::Permission => write!(f, "Permission"),
            ErrorCategory::NotFound => write!(f, "Not Found"),
            ErrorCategory::Internal => write!(f, "Internal"),
            ErrorCategory::External => write!(f, "External Service"),
            ErrorCategory::Timeout => write!(f, "Timeout"),
            ErrorCategory::Serialization => write!(f, "Serialization"),
        }
    }
}

/// Result type alias for the application
pub type AppResult<T> = Result<T, AppError>;

/// Convert from other error types
impl From<anyhow::Error> for AppError {
    fn from(err: anyhow::Error) -> Self {
        AppError::internal(err.to_string())
    }
}

impl From<serde_json::Error> for AppError {
    fn from(err: serde_json::Error) -> Self {
        AppError::serialization(err.to_string())
    }
}

impl From<std::io::Error> for AppError {
    fn from(err: std::io::Error) -> Self {
        match err.kind() {
            std::io::ErrorKind::NotFound => AppError::not_found(err.to_string()),
            std::io::ErrorKind::PermissionDenied => AppError::permission(err.to_string()),
            std::io::ErrorKind::TimedOut => AppError::timeout(err.to_string()),
            _ => AppError::internal(err.to_string()),
        }
    }
}

impl From<reqwest::Error> for AppError {
    fn from(err: reqwest::Error) -> Self {
        if err.is_timeout() {
            AppError::timeout(err.to_string())
        } else if err
            .status()
            .map_or(false, |status| status.is_client_error())
        {
            AppError::validation(err.to_string())
        } else {
            AppError::network(err.to_string())
        }
    }
}

impl From<tokio::time::error::Elapsed> for AppError {
    fn from(err: tokio::time::error::Elapsed) -> Self {
        AppError::timeout(err.to_string())
    }
}

/// Error context for better debugging
#[derive(Debug, Clone)]
pub struct ErrorContext {
    /// Operation that failed
    pub operation: String,
    /// Additional context
    pub context: std::collections::HashMap<String, String>,
    /// Timestamp
    pub timestamp: chrono::DateTime<chrono::Utc>,
}

impl ErrorContext {
    /// Create a new error context
    pub fn new<S: Into<String>>(operation: S) -> Self {
        Self {
            operation: operation.into(),
            context: std::collections::HashMap::new(),
            timestamp: chrono::Utc::now(),
        }
    }

    /// Add context key-value pair
    pub fn add<S: Into<String>, T: Into<String>>(mut self, key: S, value: T) -> Self {
        self.context.insert(key.into(), value.into());
        self
    }

    /// Add agent ID context
    pub fn agent_id(mut self, agent_id: i64) -> Self {
        self.context
            .insert("agent_id".to_string(), agent_id.to_string());
        self
    }

    /// Add provider context
    pub fn provider<S: Into<String>>(mut self, provider: S) -> Self {
        self.context.insert("provider".to_string(), provider.into());
        self
    }

    /// Add model context
    pub fn model<S: Into<String>>(mut self, model: S) -> Self {
        self.context.insert("model".to_string(), model.into());
        self
    }
}

/// Enhanced error with context
#[derive(Error, Debug)]
pub struct ContextualError {
    /// The underlying error
    #[source]
    pub error: AppError,
    /// Error context
    pub context: ErrorContext,
}

impl fmt::Display for ContextualError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} (operation: {})", self.error, self.context.operation)?;
        if !self.context.context.is_empty() {
            write!(f, " [")?;
            for (key, value) in &self.context.context {
                write!(f, "{}={}, ", key, value)?;
            }
            write!(f, "]")?;
        }
        Ok(())
    }
}

impl ContextualError {
    /// Create a new contextual error
    pub fn new(error: AppError, context: ErrorContext) -> Self {
        Self { error, context }
    }

    /// Create a contextual error with minimal context
    pub fn with_context<S: Into<String>>(error: AppError, operation: S) -> Self {
        Self::new(error, ErrorContext::new(operation))
    }
}

/// Macro for creating contextual errors
#[macro_export]
macro_rules! contextual_error {
    ($error:expr, $operation:expr) => {
        $crate::errors::ContextualError::with_context($error, $operation)
    };
    ($error:expr, $operation:expr, $($key:expr => $value:expr),*) => {
        $crate::errors::ContextualError::new(
            $error,
            $crate::errors::ErrorContext::new($operation)
                $(.add($key, $value))*
        )
    };
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_error_creation() {
        let error = AppError::config("Invalid configuration");
        assert_eq!(error.category(), ErrorCategory::Config);
        assert!(!error.is_retryable());
        assert_eq!(error.http_status(), 500);
    }

    #[test]
    fn test_retryable_errors() {
        let network_error = AppError::network("Connection failed");
        assert!(network_error.is_retryable());
        assert!(network_error.retry_delay_ms() > 0);

        let timeout_error = AppError::timeout("Request timed out");
        assert!(timeout_error.is_retryable());
    }

    #[test]
    fn test_error_context() {
        let context = ErrorContext::new("test_operation")
            .agent_id(123)
            .provider("openai")
            .model("gpt-4");

        assert_eq!(context.operation, "test_operation");
        assert_eq!(context.context.get("agent_id"), Some(&"123".to_string()));
        assert_eq!(context.context.get("provider"), Some(&"openai".to_string()));
    }

    #[test]
    fn test_contextual_error() {
        let error = AppError::validation("Invalid input");
        let contextual = ContextualError::with_context(error, "validation_test");

        assert!(contextual.to_string().contains("validation_test"));
        assert!(contextual.to_string().contains("Invalid input"));
    }
}
